import sys
from PyQt5 import QtGui, QtWidgets, QtCore
from PyQt5.QtGui import QStandardItemModel, QIcon
from PyQt5.QtWidgets import QApplication, QMainWindow, QAction, QToolBar, QSplitter, QTableView, QHeaderView, \
    QVBoxLayout, QLabel, QWidget, QLineEdit, QAbstractItemView, QStatusBar, QFileDialog, QMessageBox
from PyQt5.QtCore import Qt, QModelIndex
import pandas as pd


class MainWindow(object):
    def setupUi(self, MainWindowUI):
        MainWindowUI.setWindowTitle("Qt")
        MainWindowUI.resize(800, 600)

        # Create and configure toolbar
        tool_bar = QToolBar(MainWindowUI)
        MainWindowUI.addToolBar(tool_bar)

        # Add actions to the toolbar
        self.pre_action = QAction(QIcon(), 'Previous(O)', MainWindowUI)
        self.next_action = QAction(QIcon(), 'Next(N)', MainWindowUI)
        self.over_action = QAction(QIcon(), 'Complete(P)', MainWindowUI)

        self.over_action.setShortcut("Ctrl+O")
        self.next_action.setShortcut("Ctrl+N")
        self.pre_action.setShortcut("Ctrl+P")

        tool_bar.addActions([self.pre_action, self.next_action, self.over_action])

        # Create menu bar
        menu_bar = MainWindowUI.menuBar()
        self.save_action = QAction(QIcon(), 'Save(S)', MainWindowUI)
        self.save_action.setShortcut('Ctrl+S')
        menu_bar.addAction(self.save_action)
        menu_bar.setFont(self.fixFontSizeConfig(13))

        # Create splitter
        splitter = QSplitter(Qt.Vertical)

        # Configure QTableView
        self.select = QTableView(MainWindowUI)
        self.configureTableView(self.select)

        # Create and configure layout
        container = self.createFormLayout()
        splitter.addWidget(self.select)
        splitter.addWidget(container)

        self.statusBar = QStatusBar()
        MainWindowUI.setStatusBar(self.statusBar)
        self.statusBar.setObjectName("statusBar")
        self.statusBar.setStyleSheet("QStatusBar::item {border: 0px}")
        MainWindowUI.setCentralWidget(splitter)

        self.valueNumber = QLabel()

    def configureTableView(self, table_view):
        table_view.setFont(self.fixFontSizeConfig(12))
        table_view.setFrameShape(QtWidgets.QFrame.NoFrame)
        table_view.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
        table_view.setProperty("showDropIndicator", False)
        table_view.setDragDropOverwriteMode(False)
        table_view.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
        table_view.setTextElideMode(QtCore.Qt.ElideNone)
        table_view.setShowGrid(False)
        table_view.setGridStyle(QtCore.Qt.NoPen)
        table_view.setCornerButtonEnabled(False)
        table_view.setObjectName("select")
        table_view.horizontalHeader().setVisible(True)
        table_view.verticalHeader().setVisible(True)
        table_view.horizontalHeader().setStretchLastSection(True)
        table_view.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)

    def createFormLayout(self):
        lay = QVBoxLayout()
        lay.addWidget(self.createLabel("regName", True))
        self.label_name_value = QLabel("")
        self.label_name_value.setFont(self.fixFontSizeConfig(12))
        lay.addWidget(self.label_name_value)
        lay.addWidget(self.createLabel("value", True))
        self.value_line = QLineEdit()
        lay.addWidget(self.value_line)
        spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
        lay.addItem(spacerItem)
        container = QWidget()
        container.setLayout(lay)
        container.setStyleSheet("background-color: white;")
        return container

    def createLabel(self, text, bold):
        label = QLabel(text)
        font = self.fixFontSizeConfig(12)
        font.setBold(bold)
        label.setFont(font)
        return label

    def fixFontSizeConfig(self, size: int):
        font = QtGui.QFont()
        font.setPointSize(size)
        return font


class Run(QMainWindow):
    def __init__(self):
        super().__init__()
        self.name_dict = {
            0: {'cfg_otp_flag': {'position': 7, 'length': 8}},
            1: {'trim_iref': {'position': 5, 'length': 6},
                'cfg_vpphon': {'position': 6, 'length': 1},
                'bytel_spare': {'position': 7, 'length': 1}},
            2: {'trim_vpph': {'position': 3, 'length': 4},
                'trim_vbgr_temp': {'position': 7, 'length': 4}},
            3: {'trim_vrefglb': {'position': 3, 'length': 4},
                'trim_vneg': {'position': 7, 'length': 4}},
            4: {'trim_ilmt_reset': {'position': 3, 'length': 4},
                'trim_omoclk': {'position': 7, 'length': 4}},
            5: {'cfg_max_set_pulse_num': {'position': 3, 'length': 4},
                'cfg_max_reset_pulse_num': {'position': 7, 'length': 4}},
            6: {'cfg_max_forming_pulse_num': {'position': 3, 'length': 4},
                'cfg_clk_ready': {'position': 7, 'length': 4}},
            7: {'cfg_write_mode': {'position': 1, 'length': 2},
                'tiref_change': {'position': 6, 'length': 5},
                'cfg_vsl_sw': {'position': 7, 'length': 1}},
            8: {'trim_sa_iref_rd': {'position': 7, 'length': 8}},
            9: {'trim_sa_iref_set': {'position': 7, 'length': 8}},
            10: {'trim_sa_iref_reset': {'position': 7, 'length': 8}},
            11: {'trim_sa_iref_form': {'position': 7, 'length': 8}},
            12: {'trim_read_delay': {'position': 7, 'length': 8}},
            13: {'trim_read_delay': {'position': 3, 'length': 4},
                 'trim_sa_ota': {'position': 7, 'length': 4}},
            14: {'trim_vppyrd': {'position': 6, 'length': 7},
                 'tm_dis_en_disable': {'position': 7, 'length': 1}},
            15: {'trim_vppy_forming_max': {'position': 5, 'length': 6},
                 'cfg_vunsel_sel': {'position': 7, 'length': 2}},
            16: {'trim_vppy_set_max': {'position': 5, 'length': 6},
                 'cfg_readen_trim': {'position': 7, 'length': 2}},
            17: {'trim_vppy_reset_max': {'position': 5, 'length': 6},
                 'byte11_spare': {'position': 7, 'length': 2}},
            18: {'trim_vppy_forming_step': {'position': 2, 'length': 3},
                 'trim_vppy_set_step': {'position': 4, 'length': 2},
                 'trim_vppy_reset_step': {'position': 7, 'length': 3}},
            19: {'trim_vppy_forming': {'position': 5, 'length': 6},
                 'byte13_spare': {'position': 7, 'length': 2}},
            20: {'trim_vppy_set': {'position': 5, 'length': 6},
                 'byte14_spare': {'position': 7, 'length': 2}},
            21: {'trim_vp': {'position': 5, 'length': 6},
                 'byte15_spare': {'position': 7, 'length': 2}},
            22: {'trim_vwl_forming': {'position': 5, 'length': 6},
                 'cfg_clmt_dis_reset': {'position': 6, 'length': 1},
                 'cfg_clmt_dis_set': {'position': 7, 'length': 1}},
            23: {'trim_vwl_set': {'position': 5, 'length': 6},
                 'cfg_vnegpmp_set_add1': {'position': 6, 'length': 1},
                 'cfg_vnegpmp_set_add2': {'position': 7, 'length': 1}},
            24: {'trim_vwl_reset': {'position': 5, 'length': 6},
                 'cfg_vnegpmp_reset_add1': {'position': 6, 'length': 1},
                 'cfg_vnegpmp_reset_add2': {'position': 7, 'length': 1}},
            25: {'trim_vwl_read': {'position': 5, 'length': 6},
                 'cfg_vnegpmp_forming_add1': {'position': 6, 'length': 1},
                 'cfg_vnegpmp_forming_add2': {'position': 7, 'length': 1}},
            26: {'trim_ilmt_set_bit4': {'position': 1, 'length': 1},
                 'cfg_slice_mask_en': {'position': 2, 'length': 1},
                 'cfg_block_mask_en': {'position': 3, 'length': 1},
                 'trim_ilmt_set': {'position': 7, 'length': 4}},
            27: {'trim_clk': {'position': 5, 'length': 6},
                 'cfg_wen_delay': {'position': 7, 'length': 2}},
            28: {'tpump_setup': {'position': 7, 'length': 8}},
            29: {'tpump_disch': {'position': 7, 'length': 8}},
            30: {'tsa_ota_saen': {'position': 3, 'length': 4},
                 'tsa_saen_cmp': {'position': 7, 'length': 4}},
            31: {'tsa_cmp_lat': {'position': 3, 'length': 4},
                 'twl_disch': {'position': 5, 'length': 2},
                 'tbsl_disch': {'position': 7, 'length': 2}},
            32: {'tpw_set': {'position': 7, 'length': 8}},
            33: {'tpw_reset': {'position': 7, 'length': 8}},
            34: {'tpw_forming': {'position': 7, 'length': 8}},
            35: {'tpw_set_step': {'position': 4, 'length': 5},
                 'tbsl_pre': {'position': 6, 'length': 2},
                 'byte23_spare': {'position': 7, 'length': 1}},
            36: {'tpw_reset_step': {'position': 4, 'length': 5},
                 'byte24_spare': {'position': 7, 'length': 3}},
            37: {'tpw_forming_step': {'position': 4, 'length': 5},
                 'cfg_error_limit': {'position': 6, 'length': 2},
                 'byte25_spare': {'position': 7, 'length': 1}},
            38: {'twl_pre': {'position': 4, 'length': 5},
                 'byte26_spare': {'position': 7, 'length': 3}},
            39: {'trim_rep2': {'position': 3, 'length': 4},
                 'trim_rep3': {'position': 7, 'length': 4}},
            40: {'trim_rep4': {'position': 3, 'length': 4},
                 'trim_rep5': {'position': 7, 'length': 4}},
            41: {'tm_pat_icell_sel_bit': {'position': 7, 'length': 8}},
            42: {'tm_icell_sel_byte': {'position': 5, 'length': 6},
                 'tm_clk_ext_en': {'position': 6, 'length': 1},
                 'tm_vbl_both_dis': {'position': 7, 'length': 1}},
            43: {'tm_vmon_sel': {'position': 3, 'length': 4},
                 'tm_vbgrext_en': {'position': 4, 'length': 1},
                 'tm_irefsaext_en': {'position': 5, 'length': 1},
                 'byte43_spare': {'position': 6, 'length': 1},
                 'tm_pmposcon': {'position': 7, 'length': 1}},
            44: {'tm_vnegdis': {'position': 0, 'length': 1},
                 'tm_vpphdis': {'position': 1, 'length': 1},
                 'tm_vrefvppy2vcc': {'position': 2, 'length': 1},
                 'tm_vwl2vcc': {'position': 3, 'length': 1},
                 'tm_clkmon': {'position': 4, 'length': 1},
                 'tm_pmpclkmon': {'position': 5, 'length': 1},
                 'tm_vrefvwl2vcc': {'position': 6, 'length': 1},
                 'tm_vrefvppyrd2vdd': {'position': 7, 'length': 1}},
            45: {'tm_sa_out': {'position': 0, 'length': 1},
                 'tm_vbl_ext': {'position': 1, 'length': 1},
                 'tm_isa_mon': {'position': 2, 'length': 1},
                 'tm_iota_mon': {'position': 3, 'length': 1},
                 'tm_vblrd_mon': {'position': 4, 'length': 1},
                 'tm_ota_cur': {'position': 6, 'length': 2},
                 'tm_bypass_reset': {'position': 7, 'length': 1}},
            46: {'tm_icell_en': {'position': 0, 'length': 1},
                 'tm_enc_bypass': {'position': 1, 'length': 1},
                 'tm_dec_bypass': {'position': 2, 'length': 1},
                 'tm_ecc_through': {'position': 3, 'length': 1},
                 'tm_ecc_bypass': {'position': 4, 'length': 1},
                 'tm_spi_testmode': {'position': 5, 'length': 1},
                 'tm_spi_forming': {'position': 6, 'length': 1},
                 'tm_spi_otp_addr': {'position': 7, 'length': 1}},
            47: {'tm_ext_int': {'position': 2, 'length': 3},
                 'tm_force_setvry_fail': {'position': 3, 'length': 1},
                 'tm_force_rstvry_fail': {'position': 4, 'length': 1},
                 'tm_ireftrim_sel': {'position': 6, 'length': 2},
                 'tm_spi_rdout_en': {'position': 7, 'length': 1}},
            48: {'tm_spi_rd_high_addr': {'position': 7, 'length': 8}},
            49: {'tm_spi_rd_low_addr': {'position': 7, 'length': 8}},
            50: {'tm_actall': {'position': 3, 'length': 4},
                 'tm_wldisch': {'position': 4, 'length': 1},
                 'tm_wlvss': {'position': 6, 'length': 2},
                 'tm_bldisch_dis': {'position': 7, 'length': 1}},
            51: {'tm_rep0': {'position': 3, 'length': 4},
                 'tm_rep1': {'position': 7, 'length': 4}},
            52: {'tm_rep2': {'position': 3, 'length': 4},
                 'tm_rep3': {'position': 7, 'length': 4}},
            53: {'tm_rep4': {'position': 3, 'length': 4},
                 'tm_rep5': {'position': 7, 'length': 4}},
            54: {'tm_spi_fsm_state': {'position': 6, 'length': 7},
                 'byte36_spare ': {'position': 7, 'length': 1}},
            55: {'tm_spi_status_reg': {'position': 7, 'length': 8}},

        }
        # test_dict = {0: {'cfg_otp_flag': {'position': 7, 'length': 8}},
        #              1: {'trim_iref': {'position': 5, 'length': 6}}}
        # self.name_dict = test_dict
        self.mainUI = MainWindow()
        self.mainUI.setupUi(self)
        self.row = -1
        self.initializeModel()
        self.mainUI.select.clicked.connect(self.clickSelect)
        self.mainUI.pre_action.triggered.connect(self.onpre)
        self.mainUI.next_action.triggered.connect(self.onnext)
        self.mainUI.over_action.triggered.connect(self.onover)
        self.mainUI.save_action.triggered.connect(self.saveFile)
        self.df = pd.DataFrame()
        # self.exportData('file_name')

    def initializeModel(self):
        self.model = QStandardItemModel()
        self.model.setHorizontalHeaderLabels(['regName', 'byteNo', 'byteHex', 'position', 'length', 'value'])
        self.mainUI.select.setModel(self.model)

        stateDataList = self.selectData()
        for tmpRow in stateDataList:
            qitemRow = [QtGui.QStandardItem(ele if i == 0 else ele) for i, ele in enumerate(tmpRow)]
            self.model.appendRow(qitemRow)

        self.mainUI.select.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents)

    def clickSelect(self, index: QModelIndex):
        self.row = index.row()
        self.updateDetails()

    def updateDetails(self):
        if 0 <= self.row < self.model.rowCount():
            self.mainUI.label_name_value.setText(self.model.item(self.row, 0).text())
            self.mainUI.value_line.setText(self.model.item(self.row, 5).text())

        self.updateStatus()

    def updateStatus(self):
        count, rowCount = self.get_fill_value()
        self.mainUI.valueNumber.setText(f'{count} / {rowCount}')
        self.mainUI.statusBar.addPermanentWidget(self.mainUI.valueNumber)

    def get_fill_value(self):
        rowCount = self.model.rowCount()
        count = 0
        for i in range(rowCount):
            if self.model.item(i, 5).text() != '':
                count += 1
        return count, rowCount

    def onpre(self):
        if self.row > 0:
            self.row -= 1
        if self.row == -1:
            self.row = 0
        self.updateDetails()
        self.scrollToCurrentRow()

    def onnext(self):
        if self.row < self.model.rowCount() - 1:
            self.row += 1
        self.updateDetails()
        self.scrollToCurrentRow()

    def onover(self):
        if self.row == -1:
            self.incrementRow()
            return

        line_data = self.mainUI.value_line.text()

        if not line_data.isdigit():
            self.displayWarning('Please enter a decimal number!')
            return

        length = int(self.model.item(self.row, 4).text())
        cin = int(line_data)

        if cin > 2 ** length - 1:
            self.displayWarning('There is a problem with the input value!')
        else:
            self.updateItemData()
            self.incrementRow()


    def displayWarning(self, message):
        self.updateMessage(message, warning=True)
        self.mainUI.value_line.clear()

    def updateItemData(self):
        item = self.model.item(self.row)
        item.setData(QtGui.QColor("#FFFFFF"), QtCore.Qt.ForegroundRole)
        item.setData(QtGui.QColor("#CC0000"), QtCore.Qt.BackgroundRole)
        self.model.item(self.row, 5).setText(self.mainUI.value_line.text())

    def incrementRow(self):
        if self.row < self.model.rowCount() - 1:
            self.row += 1
        self.updateDetails()
        self.scrollToCurrentRow()
        self.mainUI.value_line.clear()

    def scrollToCurrentRow(self):
        self.mainUI.select.scrollTo(self.model.index(self.row, 0), QAbstractItemView.PositionAtCenter)
        self.mainUI.select.setCurrentIndex(self.model.index(self.row, 0))

    def saveFile(self):
        options = QFileDialog.Options()
        file_name, _ = QFileDialog.getSaveFileName(self, "Save File", "", "Csv Files (*.csv);;",
                                                   options=options)
        if file_name:
            self.updateMessage('file load success!', msecss=1000)
            count, rowCount = self.get_fill_value()

            if count != rowCount:
                self.updateMessage(f'value is not filled!exist: {count} / {rowCount}', warning=True)
            else:
                self.exportData(file_name)
        else:
            self.updateMessage('file load error!', warning=True)

    def exportData(self, file_name):
        model = self.model
        for i in range(model.rowCount()):
            byteNo = model.item(i, 1).text()
            regName = model.item(i, 0).text()
            data = model.item(i, 5).text()
            self.name_dict[int(byteNo)][regName]['data'] = int(data)

        DEC, HEX, DATA = self.mergeData()

        self.df['DEC'] = DEC
        self.df['HEX'] = HEX
        self.df['DATA'] = DATA
        self.df.to_csv(file_name, index=False)
        self.updateMessage("Save Path: " + file_name, info=True)

    def mergeData(self):
        DEC = []
        HEX = []
        DATA = []
        for byteNo, regName_dict in self.name_dict.items():
            byteHex = ['0'] * 8  # Initialize byteHex as a list of '0's
            DEC.append(byteNo)
            HEX.append(hex(byteNo)[2:])
            for regName, regName_data in regName_dict.items():
                position, length = regName_data['position'], regName_data['length']
                data = f"{regName_data['data']:0{length}b}"  # Convert data to binary with fixed length

                # Ensure start position is within the list bounds
                start = max(0, 8 - (position + 1))
                end = start + length
                # Update byteHex with the data, considering its length
                byteHex[start:end] = data.zfill(length)  # Pad with '0' to ensure the correct length

            final_hex = f"{int(''.join(byteHex), 2):02X}"
            DATA.append(final_hex)

        return DEC, HEX, DATA

    def selectData(self):
        datas = []
        for byteNo, regData in self.name_dict.items():
            for regName, regValues in regData.items():
                position = regValues['position']
                length = regValues['length']
                byteHex = hex(int(byteNo))[2:]
                datas.append([regName, str(byteNo), byteHex, str(position), str(length), ''])
        return datas

    def updateMessage(self, new_msg: str, info: bool = False, warning: bool = False, error: bool = False,
                      msecss: int = None) -> None:
        if msecss:
            self.mainUI.statusBar.showMessage(new_msg, msecss)
        else:
            self.mainUI.statusBar.showMessage(new_msg)
        if info:
            QMessageBox.information(self, "Info", new_msg)
        elif warning:
            QMessageBox.warning(self, "Warning", new_msg)
            # logger.warning(new_msg)
        elif error:
            QMessageBox.critical(self, "Error", new_msg)
            # sys.exit()
        QApplication.processEvents()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    r = Run()
    r.show()
    app.setStyle('Fusion')
    sys.exit(app.exec_())
